home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1994 November / macformat-018.iso / Utility Spectacular / Developer / Marlais 0.3.1 / gc4.1-mac / cord / de.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-07-26  |  15.9 KB  |  587 lines  |  [TEXT/R*ch]

  1. /*
  2.  * Copyright (c) 1993-1994 by Xerox Corporation.  All rights reserved.
  3.  *
  4.  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
  5.  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
  6.  *
  7.  * Permission is hereby granted to use or copy this program
  8.  * for any purpose,  provided the above notices are retained on all copies.
  9.  * Permission to modify the code and to distribute modified code is granted,
  10.  * provided the above notices are retained, and a notice that the code was
  11.  * modified is included with the above copyright notice.
  12.  *
  13.  * Author: Hans-J. Boehm (boehm@parc.xerox.com)
  14.  */
  15. /*
  16.  * A really simple-minded text editor based on cords.
  17.  * Things it does right:
  18.  *     No size bounds.
  19.  *    Inbounded undo.
  20.  *    Shouldn't crash no matter what file you invoke it on (e.g. /vmunix)
  21.  *        (Make sure /vmunix is not writable before you try this.)
  22.  *    Scrolls horizontally.
  23.  * Things it does wrong:
  24.  *    It doesn't handle tabs reasonably (use "expand" first).
  25.  *    The command set is MUCH too small.
  26.  *    The redisplay algorithm doesn't let curses do the scrolling.
  27.  *    The rule for moving the window over the file is suboptimal.
  28.  */
  29.  
  30. /* Boehm, May 19, 1994 2:20 pm PDT */
  31. #include <stdio.h>
  32. #include "gc.h"
  33. #include "cord.h"
  34.  
  35. #ifdef THINK_C
  36. #define MACINTOSH
  37. #include <ctype.h>
  38. #endif
  39.  
  40. #if defined(WIN32)
  41. #  include <windows.h>
  42. #  include "de_win.h"
  43. #elif defined(MACINTOSH)
  44. #    include <console.h>
  45. /* curses emulation. */
  46. #    define initscr()
  47. #    define endwin()
  48. #    define nonl()
  49. #    define noecho() csetmode(C_NOECHO, stdout)
  50. #    define cbreak() csetmode(C_CBREAK, stdout)
  51. #    define refresh()
  52. #    define addch(c) putchar(c)
  53. #    define standout() cinverse(1, stdout)
  54. #    define standend() cinverse(0, stdout)
  55. #    define move(line,col) cgotoxy(col + 1, line + 1, stdout)
  56. #    define clrtoeol() ccleol(stdout)
  57. #    define de_error(s) { fprintf(stderr, s); getchar(); }
  58. #    define LINES 25
  59. #    define COLS 80
  60. #else
  61. #  include <curses.h>
  62. #  define de_error(s) { fprintf(stderr, s); sleep(2); }
  63. #endif
  64. #include "de_cmds.h"
  65.  
  66. /* List of line number to position mappings, in descending order. */
  67. /* There may be holes.                          */
  68. typedef struct LineMapRep {
  69.     int line;
  70.     size_t pos;
  71.     struct LineMapRep * previous;
  72. } * line_map;
  73.  
  74. /* List of file versions, one per edit operation */
  75. typedef struct HistoryRep {
  76.     CORD file_contents;
  77.     struct HistoryRep * previous;
  78.     line_map map;    /* Invalid for first record "now" */
  79. } * history;
  80.  
  81. history now = 0;
  82. CORD current;        /* == now -> file_contents.    */
  83. size_t current_len;    /* Current file length.        */
  84. line_map current_map = 0;    /* Current line no. to pos. map     */
  85. size_t current_map_size = 0;    /* Number of current_map entries.    */
  86.                 /* Not always accurate, but reset    */
  87.                 /* by prune_map.            */
  88. # define MAX_MAP_SIZE 3000
  89.  
  90. /* Current display position */
  91. int dis_line = 0;
  92. int dis_col = 0;
  93.  
  94. # define ALL -1
  95. # define NONE - 2
  96. int need_redisplay = 0;    /* Line that needs to be redisplayed.    */
  97.  
  98.  
  99. /* Current cursor position. Always within file. */
  100. int line = 0; 
  101. int col = 0;
  102. size_t file_pos = 0;    /* Character position corresponding to cursor.    */
  103.  
  104. /* Invalidate line map for lines > i */
  105. void invalidate_map(int i)
  106. {
  107.     while(current_map -> line > i) {
  108.         current_map = current_map -> previous;
  109.         current_map_size--;
  110.     }
  111. }
  112.  
  113. /* Reduce the number of map entries to save space for huge files. */
  114. /* This also affects maps in histories.                  */
  115. void prune_map()
  116. {
  117.     line_map map = current_map;
  118.     int start_line = map -> line;
  119.     
  120.     current_map_size = 0;
  121.     for(; map != 0; map = map -> previous) {
  122.         current_map_size++;
  123.         if (map -> line < start_line - LINES && map -> previous != 0) {
  124.             map -> previous = map -> previous -> previous;
  125.         }
  126.     }
  127. }
  128. /* Add mapping entry */
  129. void add_map(int line, size_t pos)
  130. {
  131.     line_map new_map = GC_NEW(struct LineMapRep);
  132.     
  133.     if (current_map_size >= MAX_MAP_SIZE) prune_map();
  134.     new_map -> line = line;
  135.     new_map -> pos = pos;
  136.     new_map -> previous = current_map;
  137.     current_map = new_map;
  138.     current_map_size++;
  139. }
  140.  
  141.  
  142.  
  143. /* Return position of column *c of ith line in   */
  144. /* current file. Adjust *c to be within the line.*/
  145. /* A 0 pointer is taken as 0 column.         */
  146. /* Returns CORD_NOT_FOUND if i is too big.     */
  147. /* Assumes i > dis_line.             */
  148. size_t line_pos(int i, int *c)
  149. {
  150.     int j;
  151.     size_t cur;
  152.     size_t next;
  153.     line_map map = current_map;
  154.     
  155.     while (map -> line > i) map = map -> previous;
  156.     if (map -> line < i - 2) /* rebuild */ invalidate_map(i);
  157.     for (j = map -> line, cur = map -> pos; j < i;) {
  158.     cur = CORD_chr(current, cur, '\n');
  159.         if (cur == current_len-1) return(CORD_NOT_FOUND);
  160.         cur++;
  161.         if (++j > current_map -> line) add_map(j, cur);
  162.     }
  163.     if (c != 0) {
  164.         next = CORD_chr(current, cur, '\n');
  165.         if (next == CORD_NOT_FOUND) next = current_len - 1;
  166.         if (next < cur + *c) {
  167.             *c = next - cur;
  168.         }
  169.         cur += *c;
  170.     }
  171.     return(cur);
  172. }
  173.  
  174. void add_hist(CORD s)
  175. {
  176.     history new_file = GC_NEW(struct HistoryRep);
  177.     
  178.     new_file -> file_contents = current = s;
  179.     current_len = CORD_len(s);
  180.     new_file -> previous = now;
  181.     if (now != 0) now -> map = current_map;
  182.     now = new_file;
  183. }
  184.  
  185. void del_hist(void)
  186. {
  187.     now = now -> previous;
  188.     current = now -> file_contents;
  189.     current_map = now -> map;
  190.     current_len = CORD_len(current);
  191. }
  192.  
  193. /* Current screen_contents; a dynamically allocated array of CORDs    */
  194. CORD * screen = 0;
  195. int screen_size = 0;
  196.  
  197. # ifndef WIN32
  198. /* Replace a line in the curses stdscr.    All control characters are    */
  199. /* displayed as upper case characters in standout mode.  This isn't    */
  200. /* terribly appropriate for tabs.                                    */
  201. void replace_line(int i, CORD s)
  202. {
  203.     register int c;
  204.     CORD_pos p;
  205.     
  206.     if (screen == 0 || LINES > screen_size) {
  207.         screen_size = LINES;
  208.         screen = (CORD *)GC_MALLOC(screen_size * sizeof(CORD));
  209.     }
  210.     if (CORD_cmp(screen[i], s) != 0) {
  211.         move(i, 0); clrtoeol(); move(i,0);
  212. #if !defined(MACINTOSH)
  213.         /* A gross workaround for an apparent curses bug: */
  214.         if (i == LINES-1) s = CORD_substr(s, 0, CORD_len(s) - 1);
  215. #endif
  216.         CORD_FOR (p, s) {
  217.             c = CORD_pos_fetch(p) & 0x7f;
  218.             if (iscntrl(c)) {
  219.                 standout(); addch(c + 0x40); standend();
  220.             } else {
  221.                 addch(c);
  222.             }
  223.         }
  224.         screen[i] = s;
  225.     }
  226. }
  227. #else
  228. # define replace_line(i,s) invalidate_line(i)
  229. #endif
  230.  
  231. /* Return up to COLS characters of the line of s starting at pos,    */
  232. /* returning only characters after the given column.            */
  233. CORD retrieve_line(CORD s, size_t pos, unsigned column)
  234. {
  235.     CORD candidate = CORD_substr(s, pos, column + COLS);
  236.                 /* avoids scanning very long lines    */
  237.     int eol = CORD_chr(candidate, 0, '\n');
  238.     int len;
  239.     
  240.     if (eol == CORD_NOT_FOUND) eol = CORD_len(candidate);
  241.     len = (int)eol - (int)column;
  242.     if (len < 0) len = 0;
  243.     return(CORD_substr(s, pos + column, len));
  244. }
  245.  
  246. # ifdef WIN32
  247. #   define refresh();
  248.  
  249.     CORD retrieve_screen_line(int i)
  250.     {
  251.         register size_t pos;
  252.         
  253.         invalidate_map(dis_line + LINES);    /* Prune search */
  254.         pos = line_pos(dis_line + i, 0);
  255.         if (pos == CORD_NOT_FOUND) return(CORD_EMPTY);
  256.         return(retrieve_line(current, pos, dis_col));
  257.     }
  258. # endif
  259.  
  260. /* Display the visible section of the current file     */
  261. void redisplay(void)
  262. {
  263.     register int i;
  264.     
  265.     invalidate_map(dis_line + LINES);    /* Prune search */
  266.     for (i = 0; i < LINES; i++) {
  267.         if (need_redisplay == ALL || need_redisplay == i) {
  268.             register size_t pos = line_pos(dis_line + i, 0);
  269.             
  270.             if (pos == CORD_NOT_FOUND) break;
  271.             replace_line(i, retrieve_line(current, pos, dis_col));
  272.             if (need_redisplay == i) goto done;
  273.         }
  274.     }
  275.     for (; i < LINES; i++) replace_line(i, CORD_EMPTY);
  276. done:
  277.     refresh();
  278.     need_redisplay = NONE;
  279. }
  280.  
  281. int dis_granularity;
  282.  
  283. /* Update dis_line, dis_col, and dis_pos to make cursor visible.    */
  284. /* Assumes line, col, dis_line, dis_pos are in bounds.            */
  285. void normalize_display()
  286. {
  287.     int old_line = dis_line;
  288.     int old_col = dis_col;
  289.     
  290.     dis_granularity = 1;
  291.     if (LINES > 15 && COLS > 15) dis_granularity = 2;
  292.     while (dis_line > line) dis_line -= dis_granularity;
  293.     while (dis_col > col) dis_col -= dis_granularity;
  294.     while (line >= dis_line + LINES) dis_line += dis_granularity;
  295.     while (col >= dis_col + COLS) dis_col += dis_granularity;
  296.     if (old_line != dis_line || old_col != dis_col) {
  297.         need_redisplay = ALL;
  298.     }
  299. }
  300.  
  301. # if defined(WIN32)
  302. # elif defined(MACINTOSH)
  303. #        define move_cursor(x,y) cgotoxy(x + 1, y + 1, stdout)
  304. # else
  305. #        define move_cursor(x,y) move(y,x)
  306. # endif
  307.  
  308. /* Adjust display so that cursor is visible; move cursor into position    */
  309. /* Update screen if necessary.                        */
  310. void fix_cursor(void)
  311. {
  312.     normalize_display();
  313.     if (need_redisplay != NONE) redisplay();
  314.     move_cursor(col - dis_col, line - dis_line);
  315.     refresh();
  316. #   ifndef WIN32
  317.       fflush(stdout);
  318. #   endif
  319. }
  320.  
  321. /* Make sure line, col, and dis_pos are somewhere inside file.    */
  322. /* Recompute file_pos.    Assumes dis_pos is accurate or past eof    */
  323. void fix_pos()
  324. {
  325.     int my_col = col;
  326.     
  327.     if ((size_t)line > current_len) line = current_len;
  328.     file_pos = line_pos(line, &my_col);
  329.     if (file_pos == CORD_NOT_FOUND) {
  330.         for (line = current_map -> line, file_pos = current_map -> pos;
  331.              file_pos < current_len;
  332.              line++, file_pos = CORD_chr(current, file_pos, '\n') + 1);
  333.         line--;
  334.         file_pos = line_pos(line, &col);
  335.     } else {
  336.         col = my_col;
  337.     }
  338. }
  339.  
  340. #if defined(WIN32)
  341. #  define beep() Beep(1000 /* Hz */, 300 /* msecs */) 
  342. #elif defined(MACINTOSH)
  343. #    define beep() SysBeep(1)
  344. #else
  345. /*
  346.  * beep() is part of some curses packages and not others.
  347.  * We try to match the type of the builtin one, if any.
  348.  */
  349. #ifdef __STDC__
  350.     int beep(void)
  351. #else
  352.     int beep()
  353. #endif
  354. {
  355.     putc('\007', stderr);
  356.     return(0);
  357. }
  358. #endif
  359.  
  360. #   define NO_PREFIX -1
  361. #   define BARE_PREFIX -2
  362. int repeat_count = NO_PREFIX;    /* Current command prefix. */
  363.  
  364. int locate_mode = 0;            /* Currently between 2 ^Ls    */
  365. CORD locate_string = CORD_EMPTY;    /* Current search string.    */
  366.  
  367. char * arg_file_name;
  368.  
  369. #ifdef WIN32
  370. /* Change the current position to whatever is currently displayed at    */
  371. /* the given SCREEN coordinates.                    */
  372. void set_position(int c, int l)
  373. {
  374.     line = l + dis_line;
  375.     col = c + dis_col;
  376.     fix_pos();
  377.     move_cursor(col - dis_col, line - dis_line);
  378. }
  379. #endif /* WIN32 */
  380.  
  381. /* Perform the command associated with character c.  C may be an    */
  382. /* integer > 256 denoting a windows command, one of the above control    */
  383. /* characters, or another ASCII character to be used as either a     */
  384. /* character to be inserted, a repeat count, or a search string,     */
  385. /* depending on the current state.                    */
  386. void do_command(int c)
  387. {
  388.     int i;
  389.     int need_fix_pos;
  390.     FILE * out;
  391.     
  392.     if ( c == '\r') c = '\n';
  393.     if (locate_mode) {
  394.         size_t new_pos;
  395.           
  396.         if (c == LOCATE) {
  397.               locate_mode = 0;
  398.               locate_string = CORD_EMPTY;
  399.               return;
  400.         }
  401.         locate_string = CORD_cat_char(locate_string, (char)c);
  402.         new_pos = CORD_str(current, file_pos - CORD_len(locate_string) + 1,
  403.                      locate_string);
  404.         if (new_pos != CORD_NOT_FOUND) {
  405.             need_redisplay = ALL;
  406.             new_pos += CORD_len(locate_string);
  407.             for (;;) {
  408.                   file_pos = line_pos(line + 1, 0);
  409.                   if (file_pos > new_pos) break;
  410.                   line++;
  411.             }
  412.             col = new_pos - line_pos(line, 0);
  413.             file_pos = new_pos;
  414.             fix_cursor();
  415.         } else {
  416.             locate_string = CORD_substr(locate_string, 0,
  417.                               CORD_len(locate_string) - 1);
  418.             beep();
  419.         }
  420.         return;
  421.     }
  422.     if (c == REPEAT) {
  423.           repeat_count = BARE_PREFIX; return;
  424.     } else if (c < 0x100 && isdigit(c)){
  425.         if (repeat_count == BARE_PREFIX) {
  426.           repeat_count = c - '0'; return;
  427.         } else if (repeat_count != NO_PREFIX) {
  428.           repeat_count = 10 * repeat_count + c - '0'; return;
  429.         }
  430.     }
  431.     if (repeat_count == NO_PREFIX) repeat_count = 1;
  432.     if (repeat_count == BARE_PREFIX && (c == UP || c == DOWN)) {
  433.           repeat_count = LINES - dis_granularity;
  434.     }
  435.     if (repeat_count == BARE_PREFIX) repeat_count = 8;
  436.     need_fix_pos = 0;
  437.     for (i = 0; i < repeat_count; i++) {
  438.         switch(c) {
  439.           case LOCATE:
  440.             locate_mode = 1;
  441.             break;
  442.           case TOP:
  443.             line = col = file_pos = 0;
  444.             break;
  445.            case UP:
  446.              if (line != 0) {
  447.                  line--;
  448.                  need_fix_pos = 1;
  449.              }
  450.              break;
  451.            case DOWN:
  452.              line++;
  453.              need_fix_pos = 1;
  454.              break;
  455.            case PGDOWN:
  456.              line += LINES;
  457.              need_fix_pos = 1;
  458.              break;
  459.            case LEFT:
  460.              if (col != 0) {
  461.                  col--; file_pos--;
  462.              }
  463.              break;
  464.            case RIGHT:
  465.              if (CORD_fetch(current, file_pos) == '\n') break;
  466.              col++; file_pos++;
  467.              break;
  468.            case UNDO:
  469.              del_hist();
  470.              need_redisplay = ALL; need_fix_pos = 1;
  471.              break;
  472.            case BS:
  473.              if (col == 0) {
  474.                  beep();
  475.                  break;
  476.              }
  477.              col--; file_pos--;
  478.              /* fall through: */
  479.            case DEL:
  480.              if (file_pos == current_len-1) break;
  481.                  /* Can't delete trailing newline */
  482.              if (CORD_fetch(current, file_pos) == '\n') {
  483.                  need_redisplay = ALL; need_fix_pos = 1;
  484.              } else {
  485.                  need_redisplay = line - dis_line;
  486.              }
  487.              add_hist(CORD_cat(
  488.                      CORD_substr(current, 0, file_pos),
  489.                      CORD_substr(current, file_pos+1, current_len)));
  490.              invalidate_map(line);
  491.              break;
  492.            case WRITE:
  493.             if ((out = fopen(arg_file_name, "wb")) == NULL
  494.               || CORD_put(current, out) == EOF) {
  495.             de_error("Write failed\n");
  496.             need_redisplay = ALL;
  497.             } else {
  498.                 fclose(out);
  499.             }
  500.             break;
  501.            default:
  502.              {
  503.                  CORD left_part = CORD_substr(current, 0, file_pos);
  504.                  CORD right_part = CORD_substr(current, file_pos, current_len);
  505.                  
  506.                  add_hist(CORD_cat(CORD_cat_char(left_part, (char)c),
  507.                            right_part));
  508.                  invalidate_map(line);
  509.                  if (c == '\n') {
  510.                      col = 0; line++; file_pos++;
  511.                      need_redisplay = ALL;
  512.                  } else {
  513.                      col++; file_pos++;
  514.                      need_redisplay = line - dis_line;
  515.                  }
  516.                  break;
  517.              }
  518.         }
  519.     }
  520.     if (need_fix_pos) fix_pos();
  521.     fix_cursor();
  522.     repeat_count = NO_PREFIX;
  523. }
  524.  
  525. /* OS independent initialization */
  526.  
  527. void generic_init(void)
  528. {
  529.     FILE * f;
  530.     CORD initial;
  531.     
  532.     if ((f = fopen(arg_file_name, "rb")) == NULL) {
  533.          initial = "\n";
  534.     } else {
  535.         initial = CORD_from_file(f);
  536.         if (initial == CORD_EMPTY
  537.             || CORD_fetch(initial, CORD_len(initial)-1) != '\n') {
  538.             initial = CORD_cat(initial, "\n");
  539.         }
  540.     }
  541.     add_map(0,0);
  542.     add_hist(initial);
  543.     now -> map = current_map;
  544.     now -> previous = now;  /* Can't back up further: beginning of the world */
  545.     need_redisplay = ALL;
  546.     fix_cursor();
  547. }
  548.  
  549. #ifndef WIN32
  550.  
  551. main(argc, argv)
  552. int argc;
  553. char ** argv;
  554. {
  555.     int c;
  556.     CORD initial;
  557.  
  558. #if defined(MACINTOSH)
  559.     console_options.title = "\pDumb Editor";
  560.     cshow(stdout);
  561.     GC_init();
  562.     argc = ccommand(&argv);
  563. #endif
  564.     
  565.     if (argc != 2) goto usage;
  566.     arg_file_name = argv[1];
  567.     setvbuf(stdout, GC_MALLOC_ATOMIC(8192), _IOFBF, 8192);
  568.     initscr();
  569.     noecho(); nonl(); cbreak();
  570.     generic_init();
  571.     while ((c = getchar()) != QUIT) {
  572.         if (c == EOF) break;
  573.         do_command(c);
  574.     }
  575. done:
  576.     endwin();
  577.     exit(0);
  578. usage:
  579.     fprintf(stderr, "Usage: %s file\n", argv[0]);
  580.     fprintf(stderr, "Cursor keys: ^B(left) ^F(right) ^P(up) ^N(down)\n");
  581.     fprintf(stderr, "Undo: ^U    Write: ^W   Quit:^D  Repeat count: ^R[n]\n");
  582.     fprintf(stderr, "Top: ^T   Locate (search, find): ^L text ^L\n");
  583.     exit(1);
  584. }
  585.  
  586. #endif  /* !WIN32 */
  587.